#pragma once

#include "log.hpp"
#include "socket.hpp"
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>

using namespace std;

// const修饰的变量能够直接给数组定义长度
static const int MAX_NUM = sizeof(fd_set) * 8;

static const uint16_t defaultport = 8088;
static const int defaultfd = -1;
static int maxfd = -1;

class SelectServer
{
public:
    SelectServer()
    {
        for (int i = 0; i < MAX_NUM; i++)
        {
            // 将数组全部初始化为-1
            array[i] = defaultfd;
        }
    }

    void Init()
    {
        _listensock.Socket();
        _listensock.Bind(_port);
        _listensock.Listen(); // 全连接队列有5个容量(自己的设置的)
    }

    void Accepter()
    {
        string cilentip;
        uint16_t cilentport;
        int sockfd = _listensock.Accept(&cilentip, &cilentport);

        int i = 1;
        for (; i < MAX_NUM; i++)
        {
            // 如果数组里有没被占用的位置,就为新连接分配文件描述符fd
            if (array[i] != defaultfd)
                continue; // 直到找到没被占用的数组空间
            else
                break; // 此时找到了没被占用的数组空间
        }

        // 说明整个数组都没有空闲空间
        if (i == MAX_NUM)
        {
            lg(Warning, "新连接无法分配,select资源不够");
            exit(1);
        }

        // 此时的i是空闲的空间下标
        array[i] = sockfd;

        // 如果比maxfd大,就进行更新
        if (array[i] > maxfd)
            maxfd = array[i];
    }

    // 读取所有连接的内容
    void Recer(fd_set fdset)
    {
        for (int i = 1; i < MAX_NUM; i++)
        {
            // 如果该文件描述符被OS认为有数据可读
            if (FD_ISSET(array[i], &fdset))
            {
                char buffer[1024];
                ssize_t n = recv(array[i], buffer, sizeof(buffer) - 1, 0); // 暂时不考虑粘包问题
                if(n > 0)
                {
                    buffer[n] = 0;
                    cout << "sockfd is: " << array[i] << " say: " << buffer;
                }
                else // 为作方便,不管是对方关闭连接还是读失败都作统一处理
                {
                    cout << "sockfd: " << array[i] << "quit " << endl;
                    close(array[i]);
                    array[i] = defaultfd;
                }
            }
            else continue;
        }
    }

    void Start()
    {
        // 此时套接字是已经绑定好了的
        array[0] = _listensock.Fd();

        // 此时文件描述符最大的就是套接字文件描述符了
        maxfd = array[0];
        fd_set fdset;

        while (true)
        {
            FD_ZERO(&fdset);
            // 每次都要关心是否有正在等待连接的连接
            FD_SET(array[0], &fdset);
            for (int i = 0; i < MAX_NUM; i++)
            {
                if(array[i] == defaultfd)
                    continue;
                
                // 此时的文件描述符是有对应的连接,应该让OS去关注其缓冲区是否有数据可读取
                else if(array[i] != defaultfd)
                {
                    FD_SET(array[i], &fdset);
                }
            }
            // 此时可以认为所有的文件描述符都被OS关心起来(还未将结构体拷贝到内核区)

            struct timeval timeout = {5, 0};
            // 多路转移,每次经过会判断哪个文件描述符有可读的内容
            int n = select(maxfd + 1, &fdset, nullptr, nullptr, &timeout);

            if (n > 0) // 说明此时有数据可读
            {
                if (FD_ISSET(array[0], &fdset)) // 说明此时有正在等待的连接
                    Accepter();                 // 把全连接队列里正在等待的连接给全部连接上

                else // 此时没有正在等待连接的连接,但是其他连接一定有内容可读
                    Recer(fdset);
            }
            else if (n == 0) // 说明此时不仅没有正在等待的连接并且连接里也没有内容可读取
                continue;
            else // 则select出错,目前不考虑
                break;
        }
    }

    ~SelectServer()
    {
        _listensock.Close();
    }

private:
    uint16_t _port = defaultport;
    Sock _listensock;
    int array[MAX_NUM];
};